// Copyright 2023 Layer5, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/spf13/cobra"
	"gopkg.in/yaml.v2"

	"github.com/layer5io/meshery/mesheryctl/internal/cli/root"
)

// GenMarkdownTreeCustom is a modified version of GenMarkdownTree from spf13/cobra
const markdownTemplateCommand = `---
layout: default
title: %s
permalink: %s
redirect_from: %s/
type: reference
display-title: "false"
language: en
command: %s
subcommand: %s
---

`

// cmdDoc is a struct to hold the data for a command
type cmdDoc struct {
	Name        string `yaml:"name"`
	Description string `yaml:"description"`
	Usage       string `yaml:"usage"`
	Example     string `yaml:"example"`
}

// prepender is a function to prepend the frontmatter to the markdown file
func prepender(filename string) string {
	file := strings.Split(filename, ".md")
	title := filepath.Base(file[0])
	words := strings.Split(title, "-")
	if len(words) <= 1 {
		url := "reference/" + words[0] + "/main"
		return fmt.Sprintf(markdownTemplateCommand, title, url, url, words[0], "nil")
	}
	if len(words) == 3 {
		url := "reference/" + words[0] + "/" + words[1] + "/" + words[2]
		return fmt.Sprintf(markdownTemplateCommand, title, url, url, words[1], words[2])
	}
	if len(words) == 4 {
		url := "reference/" + words[0] + "/" + words[1] + "/" + words[2] + "/" + words[3]
		return fmt.Sprintf(markdownTemplateCommand, title, url, url, words[1], words[2])
	}
	url := "reference/" + words[0] + "/" + words[1]
	return fmt.Sprintf(markdownTemplateCommand, title, url, url, words[1], "nil")
}

func linkHandler(name string) string {
	base := strings.TrimSuffix(name, path.Ext(name))
	words := strings.Split(base, "-")
	if len(words) <= 1 {
		return "/main"
	}
	if len(words) == 3 {
		return strings.ToLower(words[2])
	}
	if len(words) == 4 {
		return strings.ToLower(words[2]) + "/" + strings.ToLower(words[3])
	}
	return strings.ToLower(words[1])
}

// docs is a function to generate the markdown docs for mesheryctl
func doc() {
	markDownPath := "../../docs/pages/reference/mesheryctl/" // Path for docs
	//yamlPath := "./internal/cli/root/testDoc/"

	fmt.Println("Scanning available commands...")
	cmd := root.TreePath() // Takes entire tree of mesheryctl commands

	// To skip the footer part "Auto generated by spf13/cobra.."
	cmd.DisableAutoGenTag = true

	fmt.Println("Generating markdown docs...")

	err := GenMarkdownTreeCustom(cmd, markDownPath, prepender, linkHandler)

	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Documentation generated at " + markDownPath)
}

// printOptions prints the options for a command
func printOptions(buf *bytes.Buffer, cmd *cobra.Command) error {
	flags := cmd.NonInheritedFlags()
	flags.SetOutput(buf)
	if flags.HasAvailableFlags() {
		buf.WriteString("## Options\n\n<pre class='codeblock-pre'>\n<div class='codeblock'>\n")
		flags.PrintDefaults()
		buf.WriteString("\n</div>\n</pre>\n\n")
	}

	parentFlags := cmd.InheritedFlags()
	parentFlags.SetOutput(buf)
	if parentFlags.HasAvailableFlags() {
		buf.WriteString("## Options inherited from parent commands\n\n<pre class='codeblock-pre'>\n<div class='codeblock'>\n")
		parentFlags.PrintDefaults()
		buf.WriteString("\n</div>\n</pre>\n\n")
	}
	return nil
}

// GenMarkdownCustom creates custom markdown output.
func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, manuallyAddedContent map[int]string) error {
	// InitDefaultHelpCmd add the help command to the command tree.
	cmd.InitDefaultHelpCmd()
	// InitDefaultHelpFlag add the help flag to the command tree.
	cmd.InitDefaultHelpFlag()

	buf := new(bytes.Buffer)
	// CommandPath returns the full path to this command.
	name := cmd.CommandPath()

	buf.WriteString("# " + name + "\n\n")
	buf.WriteString(cmd.Short + "\n\n")
	if len(cmd.Long) > 0 {
		buf.WriteString("## Synopsis\n\n")
		longContent := cmd.Long
		// Since the long description is used for synopsis, we need to remove the "Find more information at:" line
		findIndex := strings.Index(longContent, "Find")
		if findIndex != -1 {
			buf.WriteString(longContent[:findIndex])
		} else {
			buf.WriteString(longContent)
		}
		buf.WriteString("\n")
	}

	// check if the command is runnable
	if cmd.Runnable() {
		buf.WriteString(fmt.Sprintf("<pre class='codeblock-pre'>\n<div class='codeblock'>\n%s\n\n</div>\n</pre> \n\n", cmd.UseLine()))
	}

	// check cmd has annotations link and caption
	picLine, ok := cmd.Annotations["link"]
	if !ok {
		picLine = ""
	}
	picComment, ok := cmd.Annotations["caption"]
	if !ok {
		picComment = ""
	}

	// check if cmd has example
	if len(cmd.Example) > 0 {
		buf.WriteString("## Examples\n\n")
		var examples = strings.Split(cmd.Example, "\n")
		for i := 0; i < len(examples); i++ {
			if examples[i] != "" && examples[i] != " " && examples[i] != "	" {
				if strings.HasPrefix(examples[i], "//") {
					// Description Line
					buf.WriteString(strings.Replace(examples[i], "// ", "", -1) + "\n")
				} else {
					// Code Block Line

					buf.WriteString(fmt.Sprintf("<pre class='codeblock-pre'>\n<div class='codeblock'>\n%s\n\n</div>\n</pre> \n\n", examples[i]))
				}
			}
		}
	}

	if err := printOptions(buf, cmd); err != nil {
		return err
	}

	// If command has screenshot present
	if picLine != "" {
		buf.WriteString("## Screenshots\n\n")
		// Print Caption first
		buf.WriteString(picComment)
		buf.WriteString("\n")
		// Add markdown code for screenshot
		buf.WriteString(picLine)
		buf.WriteString("\n\n")
	}

	if len(manuallyAddedContent) > 0 {
		for _, content := range manuallyAddedContent {
			buf.WriteString("\n")
			buf.WriteString("{% include " + content + " %}" + "\n")
		}
	}

	if hasSeeAlso(cmd) {
		buf.WriteString("## See Also\n\n")
		if cmd.HasParent() {
			cmd.VisitParents(func(c *cobra.Command) {
				if c.DisableAutoGenTag {
					cmd.DisableAutoGenTag = c.DisableAutoGenTag
				}
			})
		}
		buf.WriteString("Go back to [command reference index](/reference/mesheryctl/), if you want to add content manually to the CLI documentation, please refer to the [instruction](/project/contributing/contributing-cli#preserving-manually-added-documentation) for guidance.")
		buf.WriteString("\n")
	}

	if !cmd.DisableAutoGenTag {
		buf.WriteString("###### Auto generated by spf13/cobra on " + time.Now().Format("2-Jan-2006") + "\n")
	}
	_, err := buf.WriteTo(w)
	return err
}

// hasSeeAlso checks if the command has any subcommands
func hasSeeAlso(cmd *cobra.Command) bool {
	if cmd.HasParent() {
		return true
	}
	for _, c := range cmd.Commands() {
		if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
			continue
		}
		return true
	}
	return false
}

// GenMarkdownTreeCustom creates custom markdown output.
func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error {
	for _, c := range cmd.Commands() {
		if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
			continue
		}
		if err := GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler); err != nil {
			return err
		}
	}

	basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md"
	filename := filepath.Join(dir, basename)

	manuallyAddedContent, _ := getManuallyAddedContentMap(filename)

	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = io.WriteString(f, filePrepender(filename))
	if err != nil {
		return err
	}

	err = GenMarkdownCustom(cmd, f, manuallyAddedContent)
	if err != nil {
		return err
	}
	return nil
}

func getManuallyAddedContentMap(filename string) (map[int]string, error) {
	manuallyAddedContentMap := make(map[int]string)

	_, err := os.Stat(filename)
	if err != nil {
		return manuallyAddedContentMap, err
	}

	existingContent, err := os.ReadFile(filename)
	if err != nil {
		return manuallyAddedContentMap, err
	}

	content := string(existingContent)

	includePattern := regexp.MustCompile(`\{\%\s*include\s+([^%]+)\s+\%\}`)
	includeMatches := includePattern.FindAllStringSubmatch(content, -1)
	for i, match := range includeMatches {
		// Store the include content in the map with order as the key
		manuallyAddedContentMap[i] = strings.TrimSpace(match[1])
	}
	return manuallyAddedContentMap, nil
}

// GenYamlTreeCustom creates custom yaml output.
func GenYamlTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error {
	for _, c := range cmd.Commands() {
		if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
			continue
		}
		if err := GenYamlTreeCustom(c, dir, filePrepender, linkHandler); err != nil {
			return err
		}
	}

	basename := "cmds.yml"
	filename := filepath.Join(dir, basename)
	f, err := os.OpenFile(basename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}

	_, err = io.WriteString(f, filePrepender(filename))
	if err != nil {
		return err
	}

	err = GenYamlCustom(cmd, f)
	if err != nil {
		return err
	}

	// check error before defer
	if err = f.Close(); err != nil {
		return err
	}
	defer f.Close()

	return nil
}

// GenYamlCustom generates yaml docs for the command
func GenYamlCustom(cmd *cobra.Command, w io.Writer) error {
	// init default help command and flag
	cmd.InitDefaultHelpCmd()
	cmd.InitDefaultHelpFlag()
	yamlDoc := cmdDoc{}

	yamlDoc.Name = cmd.CommandPath()
	// cmd.Short is the short description of the command
	yamlDoc.Description = cmd.Short
	yamlDoc.Usage = cmd.UseLine()
	if len(cmd.Example) > 0 {
		yamlDoc.Example = cmd.Example
	}

	fmt.Println(yamlDoc)
	final, err := yaml.Marshal(&yamlDoc)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	_, err = w.Write(final)
	if err != nil {
		return err
	}

	return nil
}

func main() {
	doc()
}
